feat(overlay): show popup card when caret is mid-line#624
Open
FuJacob wants to merge 1 commit into
Open
Conversation
When the caret sits mid-line (real characters follow it before the next line break), inline ghost text has no home: it would paint over the trailing characters. Promote any such inline presentation to the mirror card, which anchors to the caret line. This also establishes the surface fill-in-middle completions will render in. The promotion overrides an explicit Inline preference too, since inline cannot render mid-line; presentations already routed to the card keep their original, more specific reason. Drives off the existing CaretLinePosition.isAtEndOfLine signal so an end-of-line caret with later paragraphs stays inline. Adds isCaretAtEndOfLine to SuggestionOverlayGeometry (defaulted true), the .caretMidLine mirror reason, MirrorOverlayLayout anchoring for it, and 7 policy tests covering the auto/inline/mirror/per-app matrix.
Comment on lines
+30
to
+34
| /// The caret sits mid-line: real characters follow it before the next line break. Inline | ||
| /// ghost text would draw on top of those trailing characters, so the suggestion is promoted | ||
| /// to the card, which anchors to the field rect instead of the caret. This is also the | ||
| /// surface fill-in-middle completions render in, since a FIM result has no inline home. | ||
| case caretMidLine |
Contributor
There was a problem hiding this comment.
The doc comment says the card "anchors to the field rect instead of the caret" for
.caretMidLine, but MirrorOverlayLayout.computeAnchorTopY groups .caretMidLine with .userPreference and .perAppOverride, all of which anchor to the caret rect (falling back to the field rect only when the caret rect is empty). Since caret geometry is trustworthy in this case, anchoring to the caret is the intentional choice — the comment just describes the wrong rect.
Suggested change
| /// The caret sits mid-line: real characters follow it before the next line break. Inline | |
| /// ghost text would draw on top of those trailing characters, so the suggestion is promoted | |
| /// to the card, which anchors to the field rect instead of the caret. This is also the | |
| /// surface fill-in-middle completions render in, since a FIM result has no inline home. | |
| case caretMidLine | |
| /// The caret sits mid-line: real characters follow it before the next line break. Inline | |
| /// ghost text would draw on top of those trailing characters, so the suggestion is promoted | |
| /// to the card, which anchors to the caret rect (the geometry is trustworthy here), sitting | |
| /// just under the cursor the same way an inline ghost would. This is also the surface | |
| /// fill-in-middle completions render in, since a FIM result has no inline home. | |
| case caretMidLine |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When the caret sits mid-line (real characters follow it before the next line break), inline ghost text has no home: it would render on top of the text after the caret. This promotes any such presentation to the popup (mirror) card instead, anchored to the caret line. It is the first step toward fill-in-middle (FIM) completions, which structurally need the card because a mid-line completion cannot be drawn inline.
The signal is the existing
CaretLinePosition.isAtEndOfLine, so an end-of-line caret that still has later paragraphs below it stays inline (only same-line trailing characters trigger the card).Validation
App-hosted tests were run locally with signing disabled. The 7 new policy tests cover the auto / always-inline / always-mirror / per-app-override matrix at both end-of-line and mid-line carets; existing policy, caret-position, and mirror-layout suites still pass.
Linked issues
No issue number provided. Groundwork for FIM completions.
Risk / rollout notes
MidWordContinuationPolicy) previously rendered as inline ghost text overlapping the trailing characters. It now renders in the popup card. This is a net improvement for that case, but it is a visible change.CompletionRenderModePolicy.mode(gate the promotion on.autoonly). Flagging for a product call.project.yml/ pbxproj migrations. No new files (test changes live in existing files, so XcodeGen does not drift)..caretMidLinereason reuses the trustworthy-geometry path (anchors to the caret line), so there is no new positioning math.Greptile Summary
When the caret sits mid-line (real characters follow it on the same line), inline ghost text can't render without painting over those trailing characters. This PR detects that condition via the existing
CaretLinePosition.isAtEndOfLinesignal, promotes any inline result to the popup card with a newcaretMidLinemirror reason, and anchors the card to the caret rect (geometry is trustworthy here) rather than the input-field rect.CompletionRenderModePolicyis split into a publicmode(for:bundleIdentifier:)wrapper and a privatepreferenceModehelper; the wrapper applies the mid-line promotion as a single, self-contained rule that upgrades.inlineonly, leaving existing mirror reasons (e.g..caretGeometryEstimated) unchanged.SuggestionOverlayGeometrygainsisCaretAtEndOfLine: Bool(defaults totruefor backward compatibility), threaded throughSuggestionCoordinator+Acceptanceand thewithCaretRectcopy helper.MirrorOverlayLayout.computeAnchorTopYgroups.caretMidLinewith.userPreference/.perAppOverridefor caret-rect anchoring; seven new policy tests cover the full auto/always-inline/always-mirror/per-app matrix at both caret positions.Confidence Score: 4/5
Safe to merge; the change is well-scoped, fully tested, and the backward-compatible default on
isCaretAtEndOfLinepreserves all existing call sites.The logic is clean and the 7 new tests cover the full preference × caret-position matrix. The one doc comment in
CompletionRenderMode.swiftdescribes the wrong anchor rect for.caretMidLine(says field rect, implementation uses caret rect), which could mislead a future author working on FIM layout. No runtime behavior is affected.Cotabby/Models/CompletionRenderMode.swift— thecaretMidLinecase doc needs the anchor-rect description corrected before FIM work builds on it.Important Files Changed
caretMidLinetoMirrorReason; new case's doc comment incorrectly states anchoring target (field rect vs caret rect).mode(for:bundleIdentifier:)into a public wrapper plus privatepreferenceMode; adds mid-line promotion that correctly overrides.inlineonly, preserving existing mirror reasons..caretMidLinealongside.userPreference/.perAppOverride; updated docs accurately describe the change.isCaretAtEndOfLinetoSuggestionOverlayGeometrywith defaulttruefor backward compatibility; threaded correctly through init andwithCaretRect.isCaretAtEndOfLinefromFocusedInputContextintoSuggestionOverlayGeometryat the singlepresentOverlaydefinition site; all call sites inherit the value correctly.isCaretAtEndOfLineparameter tooverlayGeometryfixture factory withtruedefault; no issues.Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[presentOverlay called] --> B[Build SuggestionOverlayGeometry\nwith isCaretAtEndOfLine from context] B --> C[CompletionRenderModePolicy.mode] C --> D[preferenceMode: check userPreference\n& per-app overrides] D --> E{effectivePreference} E -->|alwaysInline| F[.inline] E -->|alwaysMirror| G[.mirror reason: userPreference\nor perAppOverride] E -->|auto| H{caretQuality == .estimated?} H -->|yes| I[.mirror reason: caretGeometryEstimated] H -->|no| F F --> J{baseMode == .inline\nAND !isCaretAtEndOfLine?} G --> K[Return baseMode unchanged] I --> K J -->|yes| L[.mirror reason: caretMidLine] J -->|no| M[.inline] L --> N[MirrorOverlayLayout: anchor to caretRect\nfallback to inputFrameRect] M --> O[Inline ghost text at caret] K --> P{reason?} P -->|caretGeometryEstimated| Q[MirrorOverlayLayout: anchor to inputFrameRect\nfallback to caretRect offset] P -->|userPreference / perAppOverride / caretMidLine| NReviews (1): Last reviewed commit: "feat(overlay): show popup card when care..." | Re-trigger Greptile